//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================
#include "ddk.h"
#include <stdio.h>
#include <time.h>
#include <ctype.h>
#include <malloc.h>
#include <misc.h>
#include <hash.h>
#include <llist.h>
#include <abc.h>
#include <fat.h>
#include <fatfile.h>

/*
 * do_write()
 *    Local routine to loop over a buffer and write it to a file
 *
 * Returns 0 on success, error code on error.
 */
int FatDir::
do_write(struct clust *c, UInt32 pos, char *buf, UInt32 cnt)
{
    UInt32 bufoff, step, blk, boff;
    struct buf *handle;
    char *pTmp = NULL;

    /*
     * Loop across each block, putting our data into place
     */
    bufoff = 0;
    while (cnt > 0) {
        /*
         * Calculate how much to take out of current block
         */
        boff = pos & (BLOCKSIZE-1);
        step = BLOCKSIZE - boff;
        if (step >= cnt) {
            step = cnt;
        }

        /*
         * Map current block
         */
        blk = pos / BLOCKSIZE;// Calculate the start pos'clusters' offset
        handle = pDiskAbc->find_buf(BOFF(c->c_clust[blk]), CLSIZE,
            ((boff == 0) && (step == BLOCKSIZE)) ? 0 : ABC_FILL);
        if (IS_ERR(handle)) {
            return PTR_ERR(handle);
        }

        if (blk > c->n_newclust && boff == 0) { // new added
            c->n_newclust = blk;
            handle->b_flags |= (B_SEC0 | B_SECS);
        }

        /*
         * Copy data, mark buffer dirty, free it
         */
        pTmp = (char *)pDiskAbc->index_buf(handle, 0, CLSIZE);
        if (NULL == pTmp) {
            pDiskAbc->unlock_buf(handle);
            return EIO;
        }
        pTmp = pTmp + boff;
        bcopy(buf + bufoff, pTmp, step);
        pDiskAbc->dirty_buf(handle, 0);
        pDiskAbc->unlock_buf(handle);
        p_Sharedata->pSyncBufEvent->Notify((EventState)handle);
        /*
         * Advance counters
         */
        pos += step;
        bufoff += step;
        cnt -= step;
    }
    return(0);
}

/*
 * write_zero()
 *    Fill in zeroes when new write position is beyond old EOF
 */
int FatDir::
write_zero(struct node *n, UInt32 oldlen, UInt32 newlen)
{
    char *zero1 = NULL;
    UInt32 count = newlen - oldlen, step;
    int ret = 0;
    const UInt32 stepLen = 1024;

    zero1 = (char *)malloc(stepLen);
    if (NULL == zero1) {
        return ENOMEM;
    }
    /*
     * Here's the zeroes we'll drop down
     */
    bzero(zero1, stepLen);

    /*
     * Write in zeroes one buffer full at a time until we've
     * caught up with our new file end.
     */
    while (count > 0) {
        if (count < stepLen) {
            step = count;
        }
        else {
            step = stepLen;
        }

        if (ret = do_write(n->n_clust, oldlen, zero1, step)) {
            free(zero1);
            return ret;
        }
        oldlen += step;
        count -= step;
    }
    free(zero1);
    return(0);
}

/*
 * fatfs_write()
 *    Write to an open file
 */
int FatDir::
fatfs_write(class CFatFile *f, unsigned char *buf, UInt32 size,
                UInt32 *Written)
{
    struct node *n = f->f_node;
    UInt32 newlen, oldlen, newclusts = 0;
    int ret = 0;

    /*
     * the sd card has been ejected...
     */
    if (p_Sharedata->m_sdCardEjected) {
        return EINVALHDL;
    }

    /*
     * Can only write to a true file, and only if open for writing.
     */
    if ((n->n_type == T_DIR) || !(f->f_perm & ACC_WRITE)) {
        return EPERM;
    }

    /*
     * See if the file's going to be able to hold all the data.  We
     * do not necessarily need to allocate space if we're rewriting
     * an existing file.
     */
    newlen = f->f_pos + size;
    oldlen = n->n_len;
    DEBUGOUT(kprintf("new:%d, old:%d\n", newlen, oldlen);
    )
    if (newlen > oldlen) {
        /*
         * Grow the size of the file to encompass the
         * starting position for this write.  Make sure
         * the initial contents is zero.  The FAT filesystem
         * is *not* very efficient with sparse files....
         */
        newclusts = roundup(newlen, p_Sharedata->clsize) / p_Sharedata->clsize;
        if (pFat->clust_setlen(n->n_clust, newclusts)) {
            return ENOSPC;
        }
        n->n_len = newlen;
        if (f->f_pos > oldlen) {
            if (ret = write_zero(n, oldlen, f->f_pos)) {
                return ret;
            }
        }
    }

    /*
     * Copy out the buffer
     *
    DEBUGOUT(kprintf(
        "before do_write:file:%d, %s\n", f->f_node->n_slot, (char *)buf);)
    for(UInt32 i = 0; i < n->n_clust->c_nclust; i++) {
        DEBUGOUT(kprintf("%d\n", n->n_clust->c_clust[i]);)
    }*/
    if (ret = do_write(n->n_clust, f->f_pos, (char *)buf, size)) {
        return ret;
    }

    n->n_flags |= N_DIRTY;

    f->f_pos += size;
    *Written = size;

    return 0;
}

/*
 * fatfs_write()
 *    Write to an open file
 */
int FatDir::
fatfs_SetSize(class CFatFile *f, UInt32 size)
{
    struct node *n = f->f_node;
    UInt32 newlen, oldlen, newclusts = 0;

    /*
     * the sd card has been ejected...
     */
    if (p_Sharedata->m_sdCardEjected) {
        return EINVALHDL;
    }

    /*
     * Can only write to a true file, and only if open for writing.
     */
    if ((n->n_type == T_DIR) || !(f->f_perm & ACC_WRITE)) {
        return EPERM;
    }

    /*
     * See if the file's going to be able to hold all the data.  We
     * do not necessarily need to allocate space if we're rewriting
     * an existing file.
     */
    newlen = size;
    oldlen = n->n_len;
    newclusts = (UInt32)(roundup((UInt64)newlen,
        (UInt64)p_Sharedata->clsize) / p_Sharedata->clsize);
    if (pFat->clust_setlen(n->n_clust, newclusts)) {
        return ENOSPC;
    }
    assert(newlen <= newclusts * p_Sharedata->clsize);
    n->n_len = newlen;

    if (newlen > oldlen) {
        write_zero(n, oldlen, newlen);
    }
    n->n_flags |= N_DIRTY;

    return 0;
}

/*
 * pack_name()
 *    Pack a FAT name into a UNIX-ish format
 */
Void pack_name(struct directory *d, wchar_t *file)
{
    char *p, *name = (char *)d->DIR_Name, *ext = (char *)d->DIR_Ext;
    char *nameEnd = name + 7, *extEnd = ext + 2;
    int fLowercase = d->DIR_NTRes & RES_LOWCASE;
    /*
     * Throw off all SPACE who at end of name or extence.
     * Some times, there are special 8.3 short file name include SPACE,
     * So we can not consider that any SPACE represent the end of name.
     */
    while ((nameEnd >= name) && (' ' == *nameEnd)) {
        nameEnd--;
    }

    while ((extEnd >= ext) && (' ' == *extEnd)) {
        extEnd--;
    }

    for (p = name; p <= nameEnd;++p) {
        *file++ = (fLowercase)?tolower(*p) : *p;
    }

    if (ext <= extEnd) {
        p = ext;
        *file++ = '.';
        for (; p <= extEnd;++p) {
            *file++ = (fLowercase)?tolower(*p) : *p;
        }
    }
    *file = '\0';
}

/*
 * unicode_char_copy()
 *    Copy a single run of chars
 *
 * Returns 0 if all characters were copied, 1 if end of string was seen
 */
int
unicode_char_copy(wchar_t *buf, UInt8 *name, int len)
{
    int x;

    for (x = 0; x < len;++x) {
        buf[x] = *(wchar_t *)&name[x * 2];
        if (buf[x] == L'\0') {
            return 1;
        }
    }
    return(0);
}

/*
 * unicode_copy()
 *    Copy the (unicode encoded) name in a VSE into a buffer
 *
 * The offset into "buf" is deduced from the dirVSE "id" value
 */
void
unicode_copy(struct dirVSE *dv, wchar_t *buf)
{
    UInt8 id = dv->dv_id & VSE_ID_MASK;

    /*
     * Index to appropriate location
     */
    ASSERT_DEBUG(id <= VSE_ID_MAX, "unicode_copy: bad ID");
    buf += (id - 1) * VSE_NAME_SIZE;

    /*
     * Assemble parts of name
     */
    int x;
    for (x = 0; x < VSE_1SIZE;++x) {
        memcpy((buf + x), &dv->dv_name1[x * 2], 2);
        if (buf[x] == L'\0')
            return;
    }

    for (x = 0; x < VSE_2SIZE;++x) {
        memcpy((buf + VSE_1SIZE +x), &dv->dv_name2[x * 2], 2);
        if (buf[x + VSE_1SIZE] == L'\0')
            return;
    }

    for (x = 0; x < VSE_3SIZE;++x) {
        memcpy((buf + VSE_1SIZE+VSE_2SIZE + x), &dv->dv_name3[x * 2], 2);
        if (buf[x + VSE_1SIZE + VSE_2SIZE] == L'\0')
            return;
    }
}

/*
 * short_checksum()
 *    Calculate the checksum value for a short FAT filename
 */
UInt8 short_checksum(char *f1, char *f2)
{
    int x;
    UInt8 sum = 0;

    for (x = 0; x < 8;++x) {
        sum = ((sum & 1) ? 0x80 : 0) + (sum >> 1) + f1[x];
    }
    for (x = 0; x < 3;++x) {
        sum = ((sum & 1) ? 0x80 : 0) + (sum >> 1) + f2[x];
    }
    return(sum);
}

/*
 * assemble_vfat_name()
 *    Extract the necessary number of VFAT sub-entries, convert to name
 *
 * Returns 0 if the name was extracted OK, 1 otherwise
 * Extracts the VSE's in any order, and expects a short name to follow.
 * Uses the checksum to verify that the VSE's match the actual dir
 * entry.
 * The initial dirVSE is supplied, since pretty much any caller has to
 * have seen it as a regular directory entry first, at which point they
 * call into here.
 */
int FatDir::
assemble_vfat_name(wchar_t *name, struct directory *d, intAssemblefun nextd,
    void *statep)
{
    struct dirVSE *dv = (struct dirVSE *)d;
    UInt32 endid = 0, mask = 0, last, id;
    UInt8 sum = dv->dv_sum;
    struct directory dirtmp;

    /*
     * Assemble VSE's until we run into the short name.  Leave if
     * we detect that our VSE's are not all present, or if we
     * detect that they don't correspond to the short name which
     * follows.
     */
    for (;;) {
        /*
         * Record ID of last VSE, mask to just offset part of ID
         */
        id = dv->dv_id;
        last = id & VSE_ID_LAST;
        id &= VSE_ID_MASK;
        if (last) {
            endid = id;
        }

        /*
         * Record bit mask of ID's seen
         */
        mask |= (1 << (id - 1));

        if ((dv->dv_id & VSE_ID_MASK)>VSE_ID_MAX) return 1;

        /*
         * Copy name into place.  Place a null termination
         * after the last VSE.  If the VSE was not fully
         * populated with characters, the VSE would have
         * contained an embedded null, and this is redundant.
         * But not harm done.
         */
        unicode_copy(dv, name);
        if (last) {
            name[id * VSE_NAME_SIZE] = '\0';
        }

        /*
         * Get next dir entry, leave when we stop assembling
         * VSE's.  Stop using supplied buffer, since we're
         * now going to modify it.
         */
        d = &dirtmp;
        dv = (struct dirVSE *)d;
        if ((*nextd)(d, statep, this)) {
            return(1);
        }
        if (d->DIR_Attr != DA_VFAT) {
            break;
        }

        /*
         * If the checksum mismatched, then we've been
         * assembling some old, moldy VSE's.  Leave.
         */
        if (dv->dv_sum != sum) {
            return(1);
        }
    }

    /*
     * Checksum of short entry must match
     */
    if (short_checksum((char *)d->DIR_Name, (char *)d->DIR_Ext) != sum) {
        return(1);
    }

    /*
     * We must see all the VSE's present, too
     */
    if ((int)mask != ((1 << endid) - 1)) {
        return(1);
    }

    /*
     * Advance past short entry, too, and return successful assembly
     * of name.
     */
    return(0);
}

/*
 * State for iterating across directory entries in a file
 */
struct dirstate
{
    struct node *ds_node;    /* File node */
    UInt32 *ds_posp;        /*  ...(pointer to) position within */
};

/*
 * next_dir()
 *    Get next dir entry out of file
 */
static int __stdcall
next_dir(void *d, void *statep, void *pFatDir)
{
    struct dirstate *state = (struct dirstate *)statep;

    if (((class FatDir *)pFatDir)->dir_copy(state->ds_node, *state->ds_posp,
        (struct directory *)d)) {
        return(1);
    }
    *state->ds_posp += 1;
    return(0);
}

/*
 * fatfs_readdir()
 *    Do reads on directory entries
 */
int FatDir::
fatfs_readdir(class CFatFile *f, unsigned char *buf, UInt32 Size,
                UInt32 *pSizeRead)
{
    UInt32 len, x;
    struct directory d;
    struct node *n = f->f_node;
    wchar_t *pswFile = NULL, *wfile = NULL;
    Boolean VFat;

    wfile = (wchar_t *)malloc(sizeof(wchar_t) * (VSE_MAX_NAME + 1));
    if (NULL == wfile) {
        return ENOMEM;
    }
    pswFile = wfile;

    /*
     * Get a buffer of the requested size, but put a sanity
     * cap on it.
     */
    len = Size;
    if (len > 4096) {
        len = 4096;
    }

    buf[0] = '\0';

    /*
     * Skip "." and "..", which exist only in non-root directories
     */
//    if ((n != p_Sharedata->rootdir) && (f->f_pos == 0)
//            && p_Sharedata->fat_size != FAT32) {
//        f->f_pos = 2;
//    }

    /*
     * Assemble as many names as will fit
     */
    for (x = 0; x < len;) {
        UInt32 c;
        UInt32 opos;
        VFat = FALSE;

        /*
         * Look at the slot at f_pos.  For reads of directories
         * f_pos is simply the struct directory index.  Leave
         * loop on failure, presumably from EOF.
         */
        opos = f->f_pos;
        if (dir_copy(n, f->f_pos++, &d)) break;

        /*
         * Leave after last entry, skip deleted entries
         */
        c = (d.DIR_Name[0] & 0xFF);
        if (!c) break;

        if ((d.DIR_Name[0]&0xFF) == DN_DEL) continue;

        if (dir_checkentry(&d) != 0) continue;

        if (d.DIR_Attr == DA_VOLUME) continue;

        if ((d.DIR_Attr & (DA_DIR | DA_VOLUME)) == DA_DIR
            && dir_checkcontent(&d) != 0) {
            // bad dir format and it's not "."/".."
            if (!(d.DIR_Name[0] == 0x2E &&
                (d.DIR_Name[1] == 0x2E || d.DIR_Name[1] == 0x20)
                && d.DIR_Name[2] == 0x20 && d.DIR_Name[3] == 0x20
                && d.DIR_Name[4] == 0x20 && d.DIR_Name[5] == 0x20
                && d.DIR_Name[6] == 0x20 && d.DIR_Name[7] == 0x20
                && d.DIR_Ext[0] == 0x20 && d.DIR_Ext[1] == 0x20
                && d.DIR_Ext[2] == 0x20)) {
                continue;
            }
        }

        /*
         * For VFAT, assemble the long name and skip the
         * short one.
         */
        if (d.DIR_Attr == DA_VFAT) {
            VFat = TRUE;
            struct dirstate state;

            state.ds_node = n;
            state.ds_posp = &f->f_pos;
            if (assemble_vfat_name(pswFile, &d, next_dir, &state)) {
                /*
                 * On failure to assemble, we need to make
                 * sure we don't skip elements which wouldn't
                 * assemble with our original starting point,
                 * but are valid after skipping that (invalid)
                 * starting point.  Stepping one back from
                 * the end might be as safe, and a bit more
                 * efficient for this edge case?
                 */
                f->f_pos = opos+1;
                continue;
            }
        }
        else {
            /*
             * Otherwise just convert the short name
             */
            pack_name(&d, pswFile);
        }

        /*
         * If the next entry won't fit, back up the file
         * position and return what we have.
         */
        x = wcslen(pswFile);
        //if the length of the file name is longer than 256Bytes, then error
        if (x > Size) {
            free(wfile);
            return EIO;
        }
        memcpy(buf, (char *)wfile, x * 2);
        buf[x * 2] = 0;//EOS
        buf[x * 2 + 1] = 0;
        break;
    }

    *pSizeRead = x;

    /*
     * Send back results
     */
    free(wfile);
    return 0;
}

/*
 * fatfs_read()
 *    Read bytes out of the current file or directory
 *
 * Directories get their own routine.
 */
int FatDir::
fatfs_read(class CFatFile *f, unsigned char *buf, UInt32 Size,
                UInt32 *pSizeRead)
{
    if (buf == NULL)
        return EINVAL;
    int x, step, cnt, blk, boff;
    struct node *n = f->f_node;
    void *handle;
    struct clust *c = n->n_clust;
    char *pTmp = NULL;
    void *handle2 = NULL;

    /*
     * the sd card has been ejected...
     */
    if (p_Sharedata->m_sdCardEjected) {
        return EINVALHDL;
    }

    /*
     * Directory
     */
    if (n->n_type == T_DIR) {
        return fatfs_readdir(f, buf, Size, pSizeRead);
    }

    /*
     * EOF?
     */
    if (f->f_pos >= n->n_len) {
        *pSizeRead = 0;
        return 0;
    }
    ASSERT_DEBUG(c->c_clust, "fatfs_read: len !clust");
    ASSERT_DEBUG(c->c_nclust > 0, "fatfs_read: clust !nclust");

    /*
     * Calculate # bytes to get
     */
    cnt = Size;
    if (cnt > (int)(n->n_len - f->f_pos)) {
        cnt = n->n_len - f->f_pos;
    }

    /*
     * Loop across each block, putting our data into place
     */
    for (x = 0; x < cnt;) {
        /*
         * Calculate how much to take out of current block
         */
        boff = f->f_pos & (BLOCKSIZE-1);
        step = BLOCKSIZE - boff;
        if (step >= (cnt-x)) {
            step = (cnt-x);
        }

        /*
         * Map current block
         */
        blk = f->f_pos / BLOCKSIZE;
        //ASSERT_DEBUG(blk < (int)c->c_nclust, "fatfs_read: bad blk");
        if (blk >= (int)c->c_nclust) {
            DPRINTF(("Notice:FAT table may be destroyed.\n"));
            assert(0);
            return EIO;
        }
        handle = pDiskAbc->find_buf(BOFF(c->c_clust[blk]), CLSIZE, ABC_FILL);
        if (IS_ERR(handle)) {
            return PTR_ERR(handle);
        }

        if ((blk + 1) < (int) c->c_nclust) {
            handle2 = pDiskAbc->find_buf(BOFF(c->c_clust[blk + 1]),
                CLSIZE, ABC_FILL | ABC_BG);
            assert(handle2);
        }
        pTmp = (char *)pDiskAbc->index_buf((struct buf *)handle, 0, CLSIZE);
        if (NULL == pTmp) {
            pDiskAbc->unlock_buf((struct buf *)handle);
            if (handle2 != NULL && !IS_ERR(handle2))
                pDiskAbc->unlock_buf((struct buf *)handle2);
            return EIO;
        }
        pTmp = pTmp + boff;
        bcopy(pTmp, buf + x, step);
        f->f_pos += step;
        pDiskAbc->unlock_buf((struct buf *)handle);
        if (handle2 != NULL && !IS_ERR(handle2)) {
            pDiskAbc->unlock_buf((struct buf *)handle2);
            handle2 = NULL;
        }

        /*
         * Advance to next chunk
         */
        x += step;
    }

    *pSizeRead = x;

    /*
     * Send back reply
     */
    return 0;
}

#if defined(_DEBUG)
void FatDir::FatDir_stat(UInt32 *Fat_secs, UInt32 *Data_secs,
                UInt32 *Cache_secs)
{
    pFat->fat_stat(Fat_secs);
    pDiskAbc->abc_bufs_stat(Data_secs, Cache_secs);
}
#endif
